package top.ruanlitao.shortlink.gateway.filter;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import reactor.core.publisher.Mono;
import top.ruanlitao.shortlink.gateway.common.constant.RedisCacheConstant;
import top.ruanlitao.shortlink.gateway.config.Config;
import top.ruanlitao.shortlink.gateway.dto.GatewayErrorResult;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Objects;

/**
 * SpringCloud Gateway Token 拦截器
 */
@Component
@Slf4j
public class TokenValidateFilter extends AbstractGatewayFilterFactory<Config> {

    private final StringRedisTemplate stringRedisTemplate;

    public TokenValidateFilter(StringRedisTemplate stringRedisTemplate) {
        super(Config.class);
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();
            String requestPath = request.getPath().toString();
            String requestMethod = request.getMethod().name();
            // 如果当前请求不在白名单中，需要进行 token 鉴权
            if (!isPathInWhiteList(requestPath, requestMethod, config.getWhitePathList())) {
                String username = request.getHeaders().getFirst("username");
                String token = request.getHeaders().getFirst("token");
                if (StringUtils.hasText(username) && StringUtils.hasText(token)) {
                    // 根据 username 查找 redis，查看当前用户是否登录了
                    Object userInfoJsonStr = stringRedisTemplate.opsForHash().get(RedisCacheConstant.USER_LOGIN_KEY + username, token);
                    if (userInfoJsonStr != null) {
                        JSONObject userInfoJsonObject = JSON.parseObject(userInfoJsonStr.toString());
                        ServerHttpRequest.Builder builder = exchange.getRequest().mutate().headers(httpHeaders -> {
                            httpHeaders.set("userId", userInfoJsonObject.getString("id"));
                            httpHeaders.set("realName", URLEncoder.encode(userInfoJsonObject.getString("realName"), StandardCharsets.UTF_8));
                        });
                        // token 鉴权成功，放行
                        return chain.filter(exchange.mutate().request(builder.build()).build());
                    }
                }
                log.error("鉴权失败");
                // token 鉴权失败，返回错误信息
                ServerHttpResponse response = exchange.getResponse();
                response.setStatusCode(HttpStatus.UNAUTHORIZED);
                return response.writeWith(Mono.fromSupplier(() -> {
                    DataBufferFactory bufferFactory = response.bufferFactory();
                    GatewayErrorResult resultMessage = GatewayErrorResult.builder()
                            .status(HttpStatus.UNAUTHORIZED.value())
                            .message("Token validation error")
                            .build();
                    return bufferFactory.wrap(JSON.toJSONString(resultMessage).getBytes());
                }));
            }
            // 对于存在于白名单中的请求，直接放行
            return chain.filter(exchange);
        };
    }

    /**
     * 检查请求是否在白名单列表中
     * @param requestPath 请求路径
     * @param requestMethod 请求方式
     * @param whitePathList 白名单列表
     * @return 请求是否在白名单中，在白名单：true，不在白名单：false
     */
    private boolean isPathInWhiteList(String requestPath, String requestMethod, List<String> whitePathList) {
        /*
        白名单请求有
        1. 登录：/api/short-link/admin/v1/user/login
        2. 判断用户名是否存在：/api/short-link/admin/v1/user/has-username
        3. 注册：POST 方法的 /api/short-link/admin/v1/user
         */
        return (!CollectionUtils.isEmpty(whitePathList) && whitePathList.stream().anyMatch(requestPath::startsWith))
                || (Objects.equals(requestPath, "/api/short-link/admin/v1/user") && Objects.equals(requestMethod, "POST"));
    }
}
